Real Guitar Hero

ECE 5725 Final Project(Wesdnesday session)
Miaocheng Li(ml2743)
Yang Chen(yc2733)


Demonstration Video


Introduction

Guitar Hero is a series of music rhythm game video games first released in 2005, in which players use a guitar-shaped game controller to simulate playing primarily lead, bass guitar, and rhythm guitar across numerous songs. Players match notes that scroll on-screen to colored fret buttons on the controller, strumming the controller in time to the music in order to score points, and keep the virtual audience excited. The games attempt to mimic many features of playing a real guitar, including the use of fast-fingering hammer-ons and pull-offs and the use of the whammy bar to alter the pitch of notes. With the introduction of Guitar Hero World Tour in 2008, the game includes support for a four-player band including vocals and drums. The series initially used mostly cover versions of songs created by WaveGroup Sound, but most recent titles feature soundtracks that are fully master recordings, and in some cases, special re-recordings, of the songs. Later titles in the series feature support for downloadable content in the form of new songs. That's been a really successful video game, everything perfect except that they use a fake guitar. So what we want to do, is a guitar hero game using a real electric guitar.


Project Objective:

  • The pitching algorithm based on FFT with tolerant turn-around latency (the expected latency is around 0.1s, since we use 4096 frames of 44.1Hz sampling to achieve a resolution of around 10Hz)
  • A video game interface players interact with, with menu, settings, gaming view, and the credit/punishment effects to display whether player hit the right node

Design & Solution

This project has a main program, then the main program will call the init program, the init program can call the choose program, the choose program can have three options (respectively bent_to_fly, c_major, e_string), choose any one of them will trigger the game program loop execution, but the game will continue to run in a loop. Before triggering, the choose program will first call the data_process program to process the data, in the game loop, it will repeatedly call the detection program and the refresh program, at the same time, the game will also continue to run a listener program pause, when the game's runtime runs out, the game program will call the settlement program

In our implementation of Fast Fourier Transform (FFT), the most critical decision pertains to the selection of window size, that is, the number of consecutive sampling frames on which the FFT is performed. Firstly, it is commonly understood that the time complexity of FFT is O(n log n), hence it is preferable to select a window size that is a power of two. Secondly, and more importantly, the required resolution must be considered. The standard sampling rate for audio streams is 44.1kHz, and the resolution of the FFT output is determined by dividing the sampling rate by the window size. Essentially, opting for a smaller window size reduces the waiting time for sufficient sampling frames but compromises resolution. Conversely, a larger window size allows for higher resolution decomposition of the original signal into frequency-domain components, at the expense of increased mandatory delay.

The question then becomes: what resolution do we need? Modern music is based on the 12-tone equal temperament, with the standard pitch A4 set at 440 Hz. This standard provides precise frequencies for each musical note and the interval between them. The required resolution should be fine enough to ensure that adjacent notes are not represented on the same frequency axis, meaning the resolution should be greater than the smallest frequency interval between notes on a guitar, which is approximately 5 Hz on the sixth string and over 10 Hz on the others. Therefore, we settled on a window size of 4096 for the FFT, yielding a resolution of 10.7 Hz.

Nonetheless, this entails a compulsory latency of approximately 0.1 second to fill a 4k window buffer before commencing the FFT. This latency is a critical consideration in applications like guitar hero games, where longer waiting times are undesirable. Although the resolution is not entirely satisfactory, we compensated by leveraging the harmonics of audio signals. Our post-FFT process involves calculating the modulus of the frequency domain list, performing an argsort, and checking if the fundamental frequency and its second harmonic are among the top five most prominent frequencies. This approach aids in better distinguishing semitones that are closely spaced

The decoupling of refreshing and sampling/detection
In the main game loop, there are several tasks, e.g. refreshing, audio stream management (sampling), detection, updating score. We found that the refreshing of the game interface is kind of different from the others, because we don't want choppy animation, so the refresh function should be called in each cycle to redraw as many times as possible in each time unit. But for the rest tasks, they are less frequently executed, because we have to wait for buffers to be filled in the background. So we decoupled the refreshing and other tasks, packaging the refreshing function into a class.
While this makes the game loop much clearer, another problem comes out. Since the refreshing function is decoupled from others, to synchronize them, we have to maintain the “now = time.time() - start_time” synchronized with each other in these two parts. And this turned to be tricky when we designed the pause-resume procedure. We will talk about this later.

Using timeline to drive the refreshing and sampling/detection
In previous labs, there were pygame examples “bounce balls” where two balls are moving on the screen. In that example, what we do is basically adding an increment (velocity) to the locations of two balls in each cycle. This works well for that because each loop cycle takes almost the same time. But for our project, between each time the “refresh” function is called, the interval differs due to varying time consumption for audio stream management and audio processing. Thus we designed several timeline arrays to drive the game loop. In the refresher, a “concern list” storing all the notes needed to be shown on the screen at certain timeslots is maintained. When the “now” variable turns greater than generation_time[idx], the note idx would be pushed into the concern list. It would be popped out once the “now” variable exceeds out_time[idx]. Similarly in the game loop, another concern list is maintained to store all the notes needed to be sampled and detected.
Another choice is to use a thread for the refresher function. But we didn't choose to do so because refresher and other procedures in the game loop could suffer from data race if they are running on separate threads.

Only start detection after the buffer is filled
To eliminate the choppy animation, another thing we found important is that, we should only start the fft detection after the buffer is filled up. Because we call stream.read(a certain size of chunk) before fft detection, the program would stall to wait for enough data from the buffer, if the buffer hasn't been filled yet. Thus, we always record the last timeslot when data was read from the buffer, and call the stream.read() function after a time interval which is greater than the expected time for the buffer to be filled. The expected interval can be calculated according to the 44.1k Hz sampling rate.

The fine-grained overlapped sampling windows
The compulsory latency of 4K sampling frames is around 0.1s. However, if the song is 60 bpm (which is fairly slow), then the appropriate window during which the note is considered right to the rhythm could be just around 0.2 or 0.3 second. Thus we may only have 2 or 3 chances to capture the note if we always wait for 4k sampling frames, which makes it impossible to distinguish the “too early”/”perfect”/”too late” timing. Thus, we used a 1K stride: For the first window, we wait for a whole of 4K frames. But for the rest, each time we wait for a new of 1K frames, popping out the first 1K of the previous 4K window, and appending the new 1K. In this way, we gain more chances to capture the note, as well as distinguishing the timing. Note that a 1K stride is what we tested to be optimal, since a smaller stride could cause overwhelming latency due to other procedures on the embedded system.

Audio Stream Management
To minimize latency, we use threading to create new audio streams. Except this, a tricky thing found on the rpi is that, we only have limited physical resources (for maybe, buffers). We cannot create infinite audio streams. In fact, we can at most create 16 audio streams. Once the number exceeds 16, the recording could be really weird, or there could be runtime error. But another point is that, we cannot stick to just one stream, since sometimes, one note starts to be sampled before the sampling and detection for the last note ends, especially when the bpm goes faster. In the end, we used a mod-4 mapping. Basically, there will be at most 4 audio streams created, and the notes would be mapped to different audio streams (modulo 4). Thus we won't violate the resource limitation, and also maintain functionality.

The pause and resume
The pause and resume turned out to be the most tricky design in the project. Before we designed the pause-resume function, we already added a background metronome to the game. Actually, the metronome is super important for audio games. Thus when resuming to the game, not only the notes should be where they were before pausing, the metronome should be played for some time so that the notes are still right to the beat rhythm.

The global_var module
There are many modules in this project, which makes it demanding to use global variables. But we found it tricky to use global variables in python. Especially when the global variable is modified in different levels of modules. We chose to use a global_var module to manage all the global variables. Basically, there is a dictionary storing all the global variables. In any other modules, we call global_var.set_value() or global_var.get_value() to interact with global variables. This eliminates the trouble due to circular imports of global variables. Also, we have special functions to update the detection results, detection timing, and the score.

Generic placeholder image

Structure of the code

                
                    real_guitar_hero/
                    │
                    ├── main.py
                    ├── block_27_shutdown.py
                    │
                    ├── audio/
                    │  ├── 55bpm.wav
                    │  ├── 60bpm.wav
                    │  ├── fail_game_over.wav
                    │  └── game_over.wav
                    │
                    ├── data/
                    │  ├── Bent_to_fly.csv
                    │  ├── C_major.csv
                    │  └── E_String.csv
                    │
                    ├── image/
                    │  ├── cover.png
                    │
                    └── util/
                        ├── __init__.py
                        ├── choose.py
                        ├── data_process.py
                        ├── detection.py
                        ├── game.py
                        ├── global_var.py
                        ├── init.py
                        ├── pause.py
                        ├── refresh.py
                        └── settlement.py
                
            

Drawings


Modules

choose.py

  • Provide an interface where users can choose one of three songs to play.
  • This interface allows users to return to the main menu.
  • pause.py

  • Display a pause button in the game interface's upper left corner.
  • Pressing this button pauses the game and opens the pause interface.
  • In the pause interface, users can choose to resume the game or return to the main menu.
  • settlement.py

  • Award 10 points for perfect matches between the note and user's play; deduct 5 points otherwise.
  • At the end of the song, display a settlement interface showing the user's total points.
  • global_var.py

  • Maintain global variables such as 'code_run', 'game_run', 'score', etc.
  • init.py

  • Serve as the first interface (main menu interface) upon opening the app.
  • game.py

  • Run the main loop of the game.
  • Call Refresher.refresh() in each loop to redraw everything.
  • Maintain a list 'concern_list' to store the notes that need detection.
  • Create a thread for the audio input stream each time a note approaches the hitbox.
  • Read from the stream buffer and call the detection() method for pitch detection.
  • For each note, send several windows of sampling frames to detection(), starting with a consecutive 4096-frame window followed by windows generated by removing the first 1024 frames and adding new ones.
  • detection.py

  • Implement the FFT pitch detection method.
  • Store the standard frequency for each fret/string on the guitar.
  • Detect if the fundamental frequency and its double harmonic are prominent in the frequency domain.
  • Update results through global_var if the correct note is detected, along with the timing (too early, perfect, too late).
  • refresh.py

  • Render the piTFT screen in each game loop cycle.
  • Maintain a 'concern_list' to store all notes that need to be drawn on the screen.
  • Draw each note depending on its timeslot in the data_process-generated array.
  • Display timing results on the screen.

  • Conclusion

    Our team developed a novel Guitar Hero software for the Raspberry Pi, uniquely designed to work with real electric guitars. The core technology underpinning this project is Fast Fourier Transform (FFT), an algorithm vital for converting complex audio signals into frequency components. This implementation provided us with numerous insights and learnings.

    The software capitalizes on FFT to accurately detect musical notes played on the guitar, translating them into in-game actions. A significant challenge was balancing FFT's window size to optimize both latency and frequency resolution. Our choice of a 4096 window size, giving a 10.7 Hz resolution, was a crucial compromise to maintain real-time responsiveness while ensuring adequate note distinction.

    As we look to the future, several enhancements are on the horizon. We aim to improve the algorithm's accuracy in note detection, especially in distinguishing closely spaced semitones. Another focus will be reducing latency further without compromising resolution, potentially exploring machine learning techniques for more efficient signal processing. Additionally, expanding compatibility to include various guitar types and incorporating user feedback to refine the gameplay experience are among our top priorities. This project not only represents a significant step in interactive music gaming but also opens avenues for more advanced audio processing applications on compact devices like the Raspberry Pi.


    Work Distribution

    Miaocheng Li

    ml2743@cornell.edu

  • Designed and applied the fft algorithm
  • Wrote the music csv files
  • Coded the user interface
  • Tested the overall system.
  • Yang Chen

    yc2733@cornell.edu

  • Designed and applied the software architecture.
  • Coded the user interface
  • Tested the overall system.

  • Parts List

    Total: $53.99


    References

    Pygame Document
    Pyaudio Document
    Numpy.fft() Document
    R-Pi GPIO Document
    Guitar Hero Wikipedia

    Code Appendix

    
    Real Guitar Hero_code
    --source code
    
     main.py
    
    
    """
    ECE5725 Wesdnesday
    Author: Yang Chen(yc2733) & Miaocheng li(ml2743)
    Date: 2023/12/07
    """
    from util import *
    import RPi.GPIO as GPIO 
    import time
    import os
    import pygame
    
    global SCORE
    SCORE = 0
    
    def GPIO17_callback(channel): # GPIO 17 quit button
        print("quit button has been pushed !!!")
        global_var.set_value('code_run', False)
        global_var.set_value('game_run', False)
    
    
    if __name__ == "__main__":
        GPIO.setmode(GPIO.BCM)
        global_var._init()
        global_var.set_value('code_run', True)
        global_var.set_value('game_run', False)
        global_var.set_value('is_regame', False)
    
        os.putenv('SDL_VIDEODRIVER', 'fbcon') # Display on piTFT
        os.putenv('SDL_FBDEV', '/dev/fb0')
        os.putenv('SDL_MOUSEDRV', 'TSLIB') # Track mouse clicks on piTFT
        os.putenv('SDL_MOUSEDEV', '/dev/input/touchscreen')
        GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(17, GPIO.FALLING, callback=GPIO17_callback, bouncetime=300)
        # pygame.mouse.set_visible(False)
    
    
    
        while global_var.get_value('code_run'):
    
            if not global_var.get_value('is_regame'):
                init.init()
    
                if not global_var.get_value('code_run'):
                    break
    
                file_name = choose.choose()
            else:
                global_var.set_value('is_regame', False)
            if file_name == "menu":
                continue
            else:
                global_var.set_value('game_run', True)        
    
            gen_time, arrive_time, det_start_time, det_end_time, out_time, stream_start_time, tab = data_process.load_tab(file_name)
            
    
    
            # print(pause_after-pause_before)
            sound = pygame.mixer.Sound("/home/pi/ece5725/project/guitar_hero/audio/60bpm_16_new.wav")
            global_var.set_value('sound', sound)
            pygame.mixer.init()
            global_var.get_value('sound').play()
            game.game_loop(gen_time, arrive_time, det_start_time, det_end_time, out_time, stream_start_time, tab)
            settlement.game_over()
    
                
    
    
    
     block_27_shutdown.py
    
    
    import RPi.GPIO as GPIO 
    import os
    import subprocess
    
    if __name__ == "__main__":
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.wait_for_edge(27, GPIO.FALLING)
        subprocess.run("sudo shutdown -h now".split(" "))
    
    
     util/_init_.py
    
    
    __all__ = ['choose', 'data_process', 'init', 'refresh', 'global_var', 'game', 'detection','settlement' ,'pause']
    
    
     util/choose.py
    
    
    import pygame
    from pygame.locals import * # for event MOUSE variables
    import os
    from util import global_var,init
    # import RPi.GPIO as GPIO
    # GPIO.setmode(GPIO.BCM)
    
    WHITE = 255, 255, 255
    BLACK = 0,0,0
    pos = 0
    x =0
    y = 0
    
    def choose():
        pygame.init()
        # pygame.mouse.set_visible(False)
    
        screen = pygame.display.set_mode((320,240))
        choose_font = pygame.font.Font(None,30)
        choose_buttons = {'E_String':(160,60), 'C_major':(160,100),'Bent_to_fly':(160,140),'Back':(240,20)}
        screen.fill(BLACK)
        choose_buttons_rect = {}
    
        for text, text_pos in choose_buttons.items():
            text_surface = choose_font.render(text,True,WHITE)
            rect = text_surface.get_rect(center=text_pos)
            screen.blit(text_surface,rect)
            choose_buttons_rect[text] = rect
        
        pygame.display.flip()
    
        while global_var.get_value('code_run'):
            for event in pygame.event.get():
                if(event.type == pygame.MOUSEBUTTONDOWN):
                    pos = pygame.mouse.get_pos()
                    x,y = pos
    
                    for(my_text, rect) in choose_buttons_rect.items():
                        if(rect.collidepoint(x,y)):
                            if(my_text == 'E_String'):
                                print("E_String pressed")
                                return my_text+".csv"
                            elif(my_text == 'Bent_to_fly'):
                                print("Bent_to_fly play")
                                return my_text+".csv"
                            elif(my_text == 'C_major'):
                                print("C_major play")
                                return my_text+".csv"
                            elif(my_text == 'Back'):
                                print('Back pressed')
                                return "menu"
                                # global_var.set_value('code_run',False)
    
    
     util/data_process.py
    
    
    import csv
    import os
    from util import global_var
    
    def load_tab(file_name):
        delta_arrive = 1.5
        delta_start = 0.2
        delta_end = 0.15
        delta_out = 1.75
        delta_stream_create = 0.015
    
        # delta_start = 0.4
        # delta_end = 0.3
    
        # print(os.getcwd())
        aug_file_name = "/home/pi/ece5725/project/guitar_hero/data/" + file_name
        with open(aug_file_name, 'r') as csvfile:
            reader = csv.reader(csvfile, delimiter=' ', quotechar='|')
            next(reader) # skip header
    
            time_slot = []
            notes = []
    
            for row in reader:
                notes.append(row[0].split(','))
                time_slot.append(notes[-1].pop(0))
                notes[-1].reverse()
                # print(', '.join(row))
        if file_name == "Bent_to_fly.csv":
            unit = 0.25
            delta_arrive = 1
            global_var.set_value("bpm",1)
        elif file_name == "E_String.csv":
            unit = 0.0625
            delta_arrive = 1
            global_var.set_value("bpm",1)
        elif file_name == "C_major.csv":
            unit = 0.25
            delta_arrive = 1
            global_var.set_value("bpm",1)
    
        gen_time = [int(time)*unit for time in time_slot]
        arrive_time = [time + delta_arrive for time in gen_time]
        det_start_time = [time - delta_start for time in arrive_time]
        det_end_time = [time + delta_end for time in arrive_time]
        out_time = [time + delta_out for time in gen_time]
        stream_start_time = [time - delta_stream_create for time in det_start_time]
        stream_start_time[0] -= 0.1
    
        return gen_time, arrive_time, det_start_time, det_end_time, out_time, stream_start_time, notes
    
    
     util/detection.py
    
    
    import numpy as np
    from util import global_var
    
    freq_strings = [[82.41,87.31,92.5,98,103.83,110,116.54,123.47,130.81,138.59,146.83,155.56,164.81,174.61,185,196,207.65,220,233.08,246.94,261.63,277.18], [110,116.54,123.47,130.81,138.59,146.83,155.56,164.81,174.61,185,196,207.65,220,233.08,246.94,261.63,277.18,293.66,311.13,329.63,349.23,369.99], [146.83,155.56,164.81,174.61,185,196,207.65,220,233.08,246.94,261.63,277.18,293.66,311.13,329.63,349.23,369.99,392,415.3,440,466.16,493.88], [196,207.65,220,233.08,246.94,261.63,277.18,293.66,311.13,329.63,349.23,369.99,392,415.3,440,466.16,493.88,523.25,554.37,587.33,622.25,659.25], [246.94,261.63,277.18,293.66,311.13,329.63,349.23,369.99,392,415.3,440,466.16,493.88,523.25,554.37,587.33,622.25,659.25,698.46,739.99,783.99,830.61], [329.63,349.23,369.99,392,415.3,440,466.16,493.88,523.25,554.37,587.33,622.25,659.25,698.46,739.99,783.99,830.61,880,932.33,987.77,1046.5,1108.73]]
    
    sr = 44100
    ts = 1.0/sr
    rs = 44100/4096
    def fft_detect(data, tab, timing):
        ref = []
        freq_domain = np.fft.fft(data)
        freq_domain = np.abs(freq_domain[:300])
        max_amp_idx = np.argsort(freq_domain)[-5:]
        detected = False
        for i in range(6):
            if tab[i] != '-1':
                ref.append(freq_strings[5-i][int(tab[i])])
                if round(ref[-1]/rs) in max_amp_idx and round(ref[-1]*2/rs) in max_amp_idx:
                    global_var.set_right_notes(i, True, timing)
                    detected = True
                    if timing == 0:
                        global_var.set_value('score',global_var.get_value('score')+10)
                    elif timing == -1:
                        global_var.set_value('score',global_var.get_value('score')-3)
                    else:
                        global_var.set_value('score',global_var.get_value('score')-5)
        return detected
    
    
     util/game.py
    
    
    from util import refresh, global_var, detection, pause
    import pyaudio
    import numpy as np
    import time
    import matplotlib.pyplot as plt
    import wave
    import pygame
    import threading
    
    
    form_1 = pyaudio.paInt16 # 16-bit resolution
    chans = 1 # 1 channel
    samp_rate = 44100 # 44.1kHz sampling rate
    # dev_index = 1
    window_size = 4096
    
    d_window_size = 1024
    def create_stream():
        id = global_var.get_value('stream_id')
    
        if id < 4:
            start_create = time.time()
            stream = global_var.get_value('pyaudio').open(format = form_1,rate = samp_rate,channels = chans, \
                input = True, \
                frames_per_buffer=d_window_size)
            print("{} takes to create stream".format(time.time()-start_create))
            global_var.set_value('audio_stream'+str(id), stream)
        else:
            stream = global_var.get_value('audio_stream'+str(id%4))
            stream.start_stream()
            stream.read(stream.get_read_available())
        global_var.set_value('stream_id', id+1)
    
    
    def game_loop(gen_time, arrive_time, det_start_time, det_end_time, out_time, stream_start_time, tab):
    
    
        audio = pyaudio.PyAudio() # Create pyaudio instantiation
        global_var.set_value('pyaudio', audio)
        sampling_16_width = audio.get_sample_size(pyaudio.paInt16)
        global_var.set_value('stream_id', 0);
        global_var.set_value('sound_start',time.time())
    
        pause.back_game()
        Ref = refresh.Refresher(gen_time, arrive_time, det_start_time, det_end_time, out_time, tab)
        stream_start_time.append(stream_start_time[-1]+5)
        concern_list = []
        idx = -1
        start_time = time.time()
        # right_note = False
        global_var.set_value('score',0)
        while(global_var.get_value("game_run")):
            if global_var.get_value("is_resume"):
                print("resume!")
                Ref.refresh()
                start_time += time.time() - global_var.get_value("pause_time")
                now = time.time() - start_time
                print(now)
            now = time.time() - start_time
            Ref.refresh()
            if (global_var.get_value("is_resume")):
                continue
            if len(concern_list) == 0:
                if stream_start_time[idx+1] < now:
                    idx += 1
                    concern_list.append(idx)
                    audio_stream_thread = threading.Thread(target=create_stream)
                    audio_stream_thread.start()
                    if idx == 16:
                        print()
                    detected = False
                    is_first_window = True
    
            if len(concern_list) and now < det_end_time[idx]:
                if is_first_window and now - det_start_time[idx] > 0.093 and global_var.get_value('audio_stream'+str(idx%4)) is not None:
                    time_1 = time.time()
                    chunk = global_var.get_value('audio_stream'+str(idx%4)).read(window_size)
                    print("{} takes to wait for 1 buffer filled".format(time.time() - time_1))
                    data = np.fromstring(chunk,dtype=np.int16)
                    is_first_window = False
                    window_end_time = now
                    this_detected = detection.fft_detect(data, tab[idx], -1)
                    detected = detected if detected else this_detected
                    det_cnt = 0
                elif not detected and not is_first_window and now - window_end_time > d_window_size/45000:
                    det_cnt += 1
                    print("{} note, dete_cnt = {}".format(idx, det_cnt))
                    try:
                        appendix = global_var.get_value('audio_stream'+str(idx%4)).read(d_window_size)
                        chunk = chunk[2*d_window_size:] + appendix
                        data = np.fromstring(chunk,dtype=np.int16)
                        timing = 0 if det_cnt < 7 else 1
                        this_detected = detection.fft_detect(data, tab[idx], timing)
                        detected = detected if detected else this_detected
                        window_end_time = now
                    except:
                        print("stream closed!")
            elif len(concern_list) and now > det_end_time[idx]:
                    pop_idx = concern_list.pop(0)
                    global_var.get_value('audio_stream'+str(pop_idx%4)).stop_stream()
                    if detected == False:
                        global_var.set_value('score',global_var.get_value('score')-10)
                        global_var.set_miss(pop_idx)
    
    
     util/global_var.py
    
    
    import threading
    import time
    def _init():  # initialize
        global _global_dict, _right_notes, score, _miss_idx
        _global_dict = {}
        _right_notes = [[False,8], [False,8], [False,8], [False,8], [False,8], [False,8]]
        _miss_idx = []
        score = 0
    
    def set_value(key, value):
        # create / set value
        _global_dict[key] = value
    
    def get_value(key):
        
        try:
            return _global_dict[key]
        except:
            print('Global Var not found.')
    
    def set_right_notes(string_id, right, timing):
        if timing < _right_notes[string_id][1]:
            _right_notes[string_id] = [right, timing]
    
    def clean_right_notes(string_id):
        _right_notes[string_id] = [False, 8]
    
    def get_right_notes():
        right_notes = []
        for i, note in enumerate(_right_notes):
            if note[0]:
                right_notes.append([i,note[1]])
        return right_notes
    
    def miss_set_alarm():
        time.sleep(0.4)
        _miss_idx.pop(0)
    
    def set_miss(idx):
        _miss_idx.append(idx)
        thread = threading.Thread(target=miss_set_alarm)
        thread.start()
    
    def get_miss_notes():
        return _miss_idx
    
    
     util/init.py
    
    
    import pygame
    from pygame.locals import * # for event MOUSE variables
    import os
    from util import global_var
    
    WHITE = 255, 255, 255
    BLACK = 0,0,0
    pos = 0
    x =0
    y = 0
    
    
    
    def init():
        pygame.init()
        pygame.mouse.set_visible(False)
    
        screen = pygame.display.set_mode((320, 240))
        cover_font = pygame.font.Font(None, 40)
        cover_buttons = {'Start':(80,180), 'Quit':(240,180)}
        screen.fill(BLACK) # Erase the Work space
        cover_buttons_rect = {} # Create a rect dictionary
    
        background_image = pygame.image.load('/home/pi/ece5725/project/guitar_hero/image/cover.png').convert()
        screen.blit(background_image, [0, 0])
    
        for text, text_pos in cover_buttons.items():
            text_surface = cover_font.render(text, True,BLACK)
            rect = text_surface.get_rect(center=text_pos)
            screen.blit(text_surface,rect)
            cover_buttons_rect[text] = rect
    
        pygame.display.flip()
    
        while global_var.get_value('code_run'):
            for event in pygame.event.get():
                if(event.type == pygame.MOUSEBUTTONDOWN):
                    pos = pygame.mouse.get_pos()
                    x,y = pos
    
                    for ( my_text, rect) in cover_buttons_rect.items(): # for saved button rects....
                        if ( rect.collidepoint (x,y) ): # if collide with mouse click....
                            if (my_text == 'Start'): # indicate correct button press
                                print ("Start pressed")
                                return
                                # Start_pressed = True
                            if (my_text == 'Quit'):
                                print ("Quit pressed")
                                global_var.set_value('code_run',False)
    
    
     util/pause.py
    
    
    import pygame
    from pygame.locals import * # for event MOUSE variables
    import os
    from util import global_var,init
    import time
    # import RPi.GPIO as GPIO
    # GPIO.setmode(GPIO.BCM)
    
    WHITE = 255, 255, 255
    BLACK = 0,0,0
    pos = 0
    x =0
    y = 0
    
    def back_game():
        screen = pygame.display.set_mode((320,240))
        num_font = pygame.font.Font(None,80)
        num_tab = {'Three':(160,120),'Two':(160,120),'One':(160,120)}
        screen.fill(BLACK)
        num_rect = {}
        time_unit = global_var.get_value("bpm")
        for num,num_pos in num_tab.items():
            num_surface = num_font.render(num,True,WHITE)
            rect = num_surface.get_rect(center=num_pos)
            screen.blit(num_surface,rect)
            num_rect[num] = rect
            pygame.display.flip()
            time.sleep(time_unit)
            screen.fill(BLACK)
        return
        # return to the game(TBD)
    
    def pause():
        pygame.init()
        # pygame.mouse.set_visible(False)
        global_var.get_value('sound').stop()
        screen = pygame.display.set_mode((320,240))
        pause_font = pygame.font.Font(None,30)
        pause_buttons = {'Resume':(160,120), 'Quit':(160,180)}
        screen.fill(BLACK)
        pause_buttons_rect = {}
    
        for text, text_pos in pause_buttons.items():
            text_surface = pause_font.render(text,True,WHITE)
            rect = text_surface.get_rect(center=text_pos)
            screen.blit(text_surface,rect)
            pause_buttons_rect[text] = rect
    
        pygame.display.flip()
    
        while global_var.get_value('code_run'):
            for event in pygame.event.get():
                if(event.type == pygame.MOUSEBUTTONDOWN):
                    pos = pygame.mouse.get_pos()
                    x,y = pos
    
                    for(pause_text,rect) in pause_buttons_rect.items():
                        if(rect.collidepoint(x,y)):
                            if(pause_text == 'Resume'):
                                print("game pause")
                                return 'back_game'
                            elif(pause_text == 'Quit'):
                                global_var.get_value('sound').stop()
                                print ("Quit pressed, back to the menu")
                                # back to the menu
                                return 'quit'
    
    
     util/refresh.py
    
    
    import time
    import pygame
    from pygame.locals import * # for event MOUSE variables
    from util import global_var,pause,init
    import threading
    import os
    
    WHITE = 255, 255, 255
    BLACK = 0,0,0
    RED = 255, 0, 0
    pos = 0
    
    class Refresher:
        def __init__(self,gen_time, arrive_time, det_start_time, det_end_time, out_time, tab):
            self.gen_time = gen_time
            self.arrive_time = arrive_time
            self.det_start_time = det_start_time
            self.det_end_time = det_end_time
            self.out_time = out_time
            self.tab = tab
            self.arrive_delta = self.arrive_time[0] - self.gen_time[0]
    
            self.screen = pygame.display.set_mode((320, 240))
            self.pause_font = pygame.font.Font(None,30)
            self.note_font = pygame.font.Font(None, 30)
            self.birth_point = 20
            self.estring_height = 50
            self.string_interval = 30
            self.hit_box = 200
    
            self.concern_list = [0]
            self.start_time = time.time()
            self.pause_time = 0
            self.refresh_cycle_start = 0
            self.milestone = 1
            global_var.set_value('is_resume',False)
    
        def refresh(self):
            self.refresh_cycle_start = time.time()
            if global_var.get_value('is_resume'):
                self.start_time += time.time() - self.pause_time
                now = time.time() - self.start_time
                for i in self.concern_list:
                    color = WHITE if self.det_start_time[i] > now else RED
    
                notes = []
                for j, fret in enumerate(self.tab[i]):
                    if fret != '-1':
                        notes.append([fret,(self.birth_point + (now-self.gen_time[i]) / self.arrive_delta * (self.hit_box-self.birth_point),j*self.string_interval + self.estring_height)])
                        text_surface = self.note_font.render(fret, True, color)
                        rect = text_surface.get_rect(center=notes[-1][1])
                        self.screen.blit(text_surface,rect)
            
    
                for i in range(6):
                    pygame.draw.line(self.screen, WHITE, (0,self.estring_height+i*self.string_interval), (320, self.estring_height+i*self.string_interval))
                pygame.draw.line(self.screen, WHITE, (self.hit_box,0), (self.hit_box,240))
    
                right_notes = global_var.get_right_notes()
                miss_idx = global_var.get_miss_notes()
                for notes in right_notes:
                    text = "perfect!" if notes[1] == 0 else "too early!" if notes[1] == -1 else "too late!"
                    text_surface = self.note_font.render(text, True, WHITE)
                    rect = text_surface.get_rect(center=(260,notes[0]*self.string_interval + self.estring_height))
                    self.screen.blit(text_surface, rect)
                for idx in miss_idx:
                    for j, fret in enumerate(self.tab[idx]):
                        if fret != '-1':
                            text = "miss!"
                            text_surface = self.note_font.render(text, True, WHITE)
                            rect = text_surface.get_rect(center=(260,j*self.string_interval + self.estring_height))
                            self.screen.blit(text_surface, rect)
                pygame.display.flip()
                delta = (self.pause_time - self.start_time + 3 * global_var.get_value('bpm')) % (4 * global_var.get_value('bpm'))
                global_var.get_value('sound').play()
                time.sleep(delta)
                self.start_time += delta
                global_var.set_value('is_resume',False)
                
    
            now = time.time() - self.start_time
    
            if now - self.arrive_time[-1] > 3:
                global_var.set_value("game_run", False)
                return
            self.screen.fill(BLACK)
            try:
                i = self.concern_list[-1] + 1
            except:
                return
            notes = []
            while i < len(self.gen_time):
                if self.gen_time[i] < now:
                    self.concern_list.append(i)
                    i += 1
                else:
                    break
            
            while len(self.concern_list) and self.out_time[self.concern_list[0]] < now:
                i = self.concern_list.pop(0)
                for j, fret in enumerate(self.tab[i]):
                    if fret != '-1':
                        global_var.clean_right_notes(j)
                
            for i in self.concern_list:
                color = WHITE if self.det_start_time[i] > now else RED
                for j, fret in enumerate(self.tab[i]):
                    if fret != '-1':
                        notes.append([fret,(self.birth_point + (now-self.gen_time[i]) / self.arrive_delta * (self.hit_box-self.birth_point),j*self.string_interval + self.estring_height)])
                        text_surface = self.note_font.render(fret, True, color)
                        rect = text_surface.get_rect(center=notes[-1][1])
                        self.screen.blit(text_surface,rect)
    
            
    
            for i in range(6):
                pygame.draw.line(self.screen, WHITE, (0,self.estring_height+i*self.string_interval), (320, self.estring_height+i*self.string_interval))
            pygame.draw.line(self.screen, WHITE, (self.hit_box,0), (self.hit_box,240))
    
            right_notes = global_var.get_right_notes()
            miss_idx = global_var.get_miss_notes()
            for notes in right_notes:
                text = "perfect!" if notes[1] == 0 else "too early!" if notes[1] == -1 else "too late!"
                text_surface = self.note_font.render(text, True, WHITE)
                rect = text_surface.get_rect(center=(260,notes[0]*self.string_interval + self.estring_height))
                self.screen.blit(text_surface, rect)
            for idx in miss_idx:
                for j, fret in enumerate(self.tab[idx]):
                    if fret != '-1':
                        text = "miss!"
                        text_surface = self.note_font.render(text, True, WHITE)
                        rect = text_surface.get_rect(center=(260,j*self.string_interval + self.estring_height))
                        self.screen.blit(text_surface, rect)
    
            score_text_surface = self.note_font.render('score: {}'.format(global_var.get_value('score')), True, WHITE)
            pause_text_surface = self.pause_font.render('Pause',True,WHITE)
            pause_rect = pause_text_surface.get_rect(center=(60,20))
            score_rect = score_text_surface.get_rect(center=(260,20))
            self.screen.blit(score_text_surface, score_rect)
            self.screen.blit(pause_text_surface, pause_rect)
            pygame.display.flip()
            x, y = -1, -1
            for event in pygame.event.get():
                    if(event.type == pygame.MOUSEBUTTONDOWN):
                        pos = pygame.mouse.get_pos()
                        x,y = pos
                    if(pause_rect.collidepoint(x,y)):
                        print("pause pressed")
                        self.pause_time = time.time()
                        commond = pause.pause()
                        if commond == 'back_game':
                            pause.back_game()
                            global_var.set_value('is_resume',True)
                            global_var.set_value("pause_time", self.pause_time)
                            global_var.set_value("pause_time_delta", (self.pause_time - self.start_time + 3 * global_var.get_value('bpm')) % (4 * global_var.get_value('bpm')))
                            # back to the game!!!!! HERE!!!!!
                        elif commond == 'quit':
                            global_var.set_value('game_run',False)
    
    
     util/settlement.py
    
    
    import pygame
    from pygame.locals import * # for event MOUSE variables
    import os
    from util import global_var
    # import RPi.GPIO as GPIO
    # GPIO.setmode(GPIO.BCM)
    
    
    WHITE = 255, 255, 255
    BLACK = 0,0,0
    pos = 0
    x =0
    y = 0
    
    def game_over():
        global_var.get_value('sound').stop()
        if global_var.get_value('score') < 0:
            game_over_sound = pygame.mixer.Sound("/home/pi/ece5725/project/guitar_hero/audio/fail_game_over_16.wav")
        else:
            game_over_sound = pygame.mixer.Sound("/home/pi/ece5725/project/guitar_hero/audio/game_over_16.wav")
        game_over_sound.play()
        pygame.init()
        # SCORE = global_var.get_value('score')
        print(global_var.get_value('score'))
        SCORE_label = 'your score is ' + str(global_var.get_value('score'))
        # pygame.mouse.set_visible(False)
    
        screen = pygame.display.set_mode((320,240))
        final_font = pygame.font.Font(None,30)
        final_buttons = {SCORE_label:(160,100),'REGAME':(80,140),'Quit':(240,140)}
        screen.fill(BLACK)
        final_buttons_rect = {}
    
        for text, text_pos in final_buttons.items():
            text_surface = final_font.render(text,True,WHITE)
            rect = text_surface.get_rect(center=text_pos)
            screen.blit(text_surface,rect)
            final_buttons_rect[text] = rect
    
        pygame.display.flip()
        
        global_var.set_value('pause_run',True)
    
        while global_var.get_value('pause_run'):
            for event in pygame.event.get():
                if(event.type == pygame.MOUSEBUTTONDOWN):
                    pos = pygame.mouse.get_pos()
                    x,y = pos
    
                    for(my_text, rect) in final_buttons_rect.items():
                        if(rect.collidepoint(x,y)):
                            if(my_text == 'REGAME'):
                                game_over_sound.stop()
                                print("regame")
                                global_var.set_value('score',0)
                                global_var.set_value('is_regame', True)
                                global_var.set_value('pause_run', False)
                            elif(my_text == 'Quit'):
                                game_over_sound.stop()
                                global_var.set_value('score',0)
                                print("game over")
                                global_var.set_value('pause_run',False)